/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the GLQuake source code.

 The GLQuake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The GLQuake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the GLQuake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_snapshot.cpp - writes a snapshot to the client-game
//


#include "sg_local.h"


/*
 ==============================================================================

 SNAPSHOT BUILDING

 ==============================================================================
*/

struct entityNumbers_t {
	int		numEntities;
	int		entities[MAX_SNAPSHOT_ENTITIES];
};


/*
 ==================
 SG_SortEntityNumbers
 ==================
*/
static int SG_SortEntityNumbers (const void *elem1, const void *elem2){

	int		entNum1 = *(const int *)elem1;
	int		entNum2 = *(const int *)elem2;

	if (entNum1 == entNum2)
		G_Error("SG_SortEntityNumbers: duplicated entity");

	return entNum1 - entNum2;
}

/*
 ==================
 
 ==================
*/
static bool SG_IsEntityVisible (etEntity *entity, int viewFlag, const byte *pvs){

	if (!(entity->flags & FL_VISIBLE))
		return false;

	if (entity->flags & FL_BROADCAST){
		entity->state.flags |= (viewFlag | EF_VIEW_MIRROR);
		return true;
	}

	// Check the PVS

	// It is visible
	entity->state.flags |= (viewFlag | EF_VIEW_MIRROR);

	return true;
}

/*
 ==================
 SG_IsEntityAudible
 ==================
*/
static bool SG_IsEntityAudible (etEntity *entity, int viewFlag, const glqVec3 &viewOrigin){

	if (!(entity->flags & FL_AUDIBLE))
		return false;

	if (entity->flags & FL_BROADCAST)
		return true;

	// Ignore sounds from subviews
	if (viewFlag != EF_VIEW_MAIN)
		return false;

	// Check if in range
	if (entity->audibleRange > 0.0f){
		if (viewOrigin.DistanceSqr(entity->currentOrigin) > Square(entity->audibleRange))
			return false;
	}

	// It is audible
	return true;
}

/*
 ==================
 SG_AddEntity
 ==================
*/
static void SG_AddEntity (entityNumbers_t *entityNumbers, etEntity *entity){

	// Don't duplicate entities
	if (entity->snapshotCount == level.snapshotCount)
		return;

	entity->snapshotCount = level.snapshotCount;

	// Add it
	if (entityNumbers->numEntities == MAX_SNAPSHOT_ENTITIES)
		return;

	entityNumbers->entities[entityNumbers->numEntities++] = entity->state.number;
}

/*
 ==================

 ==================
*/
static void SG_AddEntitiesVisibleFromPoint (etClient *client, entityNumbers_t *entityNumbers, int viewFlag, const glqVec3 &viewOrigin){

	etEntity	*entity;
	clipLeaf_t	*leaf;
	const byte	*pvs;
	int			i;

	glqMemory->Fill(&pvs, 255, sizeof(pvs));		// FIXME!!!

	// Get the current view leaf

	// Get the PVS for the current view leaf

	// Check all the entities
	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		// Never send unlinked entities
		if (!entity->isLinked)
			continue;

		// Never send the client's own entity, because it can be regenerated
		// from the player state
		if (entity->client == client)
			continue;

		// Never send hidden entities
		if (entity->flags & FL_HIDDEN)
			continue;

		// Entities can be flagged to explicitly not be sent to clients
		if (entity->flags & FL_NOCLIENT)
			continue;

		// Entities can be flagged to be sent to only one client
		if (entity->flags & FL_SINGLECLIENT){
			if (entity->singleClient != client->number)
				continue;
		}

		// Entities can be flagged to be sent to everyone but one client
		if (entity->flags & FL_NOTSINGLECLIENT){
			if (entity->singleClient == client->number)
				continue;
		}

		// Check if visible
		if (SG_IsEntityVisible(entity, viewFlag, pvs))
			entity->state.flags |= EF_VISIBLE;

		// Check if audible
		if (SG_IsEntityAudible(entity, viewFlag, viewOrigin))
			entity->state.flags |= EF_AUDIBLE;

		// Don't duplicate entities
		if (entity->snapshotCount == level.snapshotCount)
			continue;

		// Add the entity if needed
		if (entity->flags & FL_BROADCAST)
			SG_AddEntity(entityNumbers, entity);
		else {
			if (!(entity->state.flags & (EF_VISIBLE | EF_AUDIBLE)))
				continue;

			SG_AddEntity(entityNumbers, entity);
		}

		// If this is a remote camera entity, add all the entities
		// visible from its target position
	}
}

/*
 ==================
 SG_BuildClientSnapshot

 Decides which entities are going to be visible to the client, and copies off
 the player state.

 This properly handles multiple recursive subviews, but the renderer currently
 doesn't.
 ==================
*/
static void SG_BuildClientSnapshot (etClient *client, snapshot_t *snapshot, int sequence){

	entityNumbers_t	entityNumbers;
	etEntity		*entity;
	int				i;

	// Bump snapshot count
	level.snapshotCount++;

	// Clear entity numbers
	entityNumbers.numEntities = 0;

	// Set the initial entity state flags
	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		entity->state.flags = 0;

		// TODO: add more flags if needed

		// Blocked entities should stop moving and rotating on the client
		if (entity->flags & FL_BLOCKED)
			entity->state.flags |= EF_BLOCKED;

		// Teleported entities should not interpolate on the client
		if (entity->flags & FL_TELEPORT)
			entity->state.flags |= EF_TELEPORT;

		// Bind masters need to be sent even if unlinked or hidden, so just
		// tell the client to ignore them for anything other than binding
		if (!entity->isLinked || (entity->flags & FL_HIDDEN))
			entity->state.flags |= EF_IGNORE;
	}

	// Add all the entities directly visible from the view origin, which may
	// include subviews that merge other entities
	SG_AddEntitiesVisibleFromPoint(client, &entityNumbers, EF_VIEW_MAIN, client->GetViewOrigin());

	// If there were subviews, there may be out of order entities in the list
	// which will need to be resorted for the delta compression to work
	// correctly. This also catches the error condition of an entity being
	// included twice.
	qsort(entityNumbers.entities, entityNumbers.numEntities, sizeof(int), SG_SortEntityNumbers);

	// Set up the snapshot
	snapshot->sequence = sequence;

	// Copy the player state
	glqMemory->Copy(&snapshot->playerState, &client->state, sizeof(playerState_t));

	// Copy the entity states
	snapshot->numEntities = entityNumbers.numEntities;

	for (i = 0; i < entityNumbers.numEntities; i++){
		entity = &level.entities[entityNumbers.entities[i]];

		glqMemory->Copy(&snapshot->entityStates[i], &entity->state, sizeof(entityState_t));
	}
}


/*
 ==============================================================================

 SNAPSHOT WRITING

 ==============================================================================
*/


/*
 ==================
 SG_WriteSnapshotEntities
 ==================
*/
static void SG_WriteSnapshotEntities (msg_t *msg, snapshot_t *oldSnapshot, snapshot_t *newSnapshot){

	entityState_t	*oldState = NULL, *newState = NULL;
	int				oldIndex = 0, newIndex = 0;
	int				oldNum, newNum;
	int				oldNumEntities;

	// Generate the delta update
	if (!oldSnapshot)
		oldNumEntities = 0;
	else
		oldNumEntities = oldSnapshot->numEntities;

	while (newIndex < newSnapshot->numEntities || oldIndex < oldNumEntities){
		if (newIndex >= newSnapshot->numEntities)
			newNum = 9999;
		else {
			newState = &newSnapshot->entityStates[newIndex];
			newNum = newState->number;
		}

		if (oldIndex >= oldNumEntities)
			oldNum = 9999;
		else {
			oldState = &oldSnapshot->entityStates[oldIndex];
			oldNum = oldState->number;
		}

		if (newNum == oldNum){
			// Delta from previous state.
			// Because the force parameter is false, this will not result in
			// any bytes being emitted if the entity has not changed at all.
			SG_WriteDeltaEntityState(msg, oldState, newState, false);

			oldIndex++;
			newIndex++;

			continue;
		}

		if (newNum < oldNum){
			// This is a new entity, send it from the baseline
			SG_WriteDeltaEntityState(msg, &level.baselines[newNum], newState, true);

			newIndex++;

			continue;
		}

		if (newNum > oldNum){
			// The old entity isn't present in the new snapshot
			SG_WriteDeltaEntityState(msg, oldState, NULL, true);

			oldIndex++;

			continue;
		}
	}

	// End of snapshot entities
	glqMsgSystem->WriteBits(msg, ENTITYNUM_NONE, ES_NUMBER_BITS);
}

/*
 ==================
 SG_WriteSnapshotToClient
 ==================
*/
void SG_WriteSnapshotToClient (etClient *client, msg_t *msg, int sequence, int deltaMessage, bool rateDelayed){

	snapshot_t	*oldSnapshot, newSnapshot;
	int			i;

	// Try to use a previous snapshot as the source for delta compressing the
	// snapshot
	if (deltaMessage <= 0){
		// Client is asking for a retransmit
		oldSnapshot = NULL;

		deltaMessage = -1;
	}
	else if (sequence - deltaMessage >= (PACKET_BACKUP - 3)){
		// Client hasn't gotten a good message through in a long time
		G_DPrintf(S_COLOR_YELLOW "Delta request from out of date packet from %s\n", client->playerName);

		oldSnapshot = NULL;

		deltaMessage = -1;
	}
	else {
		// We have a valid snapshot to delta from
		oldSnapshot = client->snapshots[deltaMessage & PACKET_MASK];
	}

	// Free old snapshots that are no longer needed
	for (i = 0; i < PACKET_BACKUP; i++){
		if (!client->snapshots[i])
			continue;

		if (client->snapshots[i]->sequence >= deltaMessage)
			continue;

		glqMemory->Free(client->snapshots[i]);
		client->snapshots[i] = NULL;
	}

	// Build the snapshot
	SG_BuildClientSnapshot(client, &newSnapshot, sequence);

	// Save the snapshot in the backup array for later delta comparisons
	client->snapshots[sequence & PACKET_MASK] = (snapshot_t *)glqMemory->DupData(&newSnapshot, sizeof(snapshot_t), TAG_SERVER);

	// Write the snapshot header
	glqMsgSystem->WriteLong(msg, level.time);
	glqMsgSystem->WriteShort(msg, client->ping);
	glqMsgSystem->WriteBool(msg, rateDelayed);
	glqMsgSystem->WriteLong(msg, deltaMessage);

	// Write the player state
	if (!oldSnapshot)
		SG_WriteDeltaPlayerState(msg, NULL, &newSnapshot.playerState);
	else
		SG_WriteDeltaPlayerState(msg, &oldSnapshot->playerState, &newSnapshot.playerState);

	// Write the entities
	SG_WriteSnapshotEntities(msg, oldSnapshot, &newSnapshot);

	// Align to the next byte boundary
	glqMsgSystem->WriteAlign(msg);
}